﻿using System.Collections.Generic;
using System.Diagnostics;
using Language.Parser;
using Microsoft.VisualStudio.Text;
using Lexer = lexer.managed.Lexer;

namespace Language.Intellisense.Signature
{
    /// <summary>
    /// Used to parse function calls and provide back the information required for signature help
    /// </summary>
    internal class Parser
    {
        #region Private Types

        /// <summary>
        /// Helper class to encapsulate stepping over tokens provided by the lexer
        /// </summary>
        private class TokenIterator
        {
            #region Public Properties

            public IList<Token> Tokens { get; private set; }
            public int Index { get; private set; }

            public Token Current => GetToken( Index );
            public Token Next => GetToken( Index + 1 );
            public Token Previous => GetToken( Index - 1 );

            public bool End => Index >= Tokens.Count;

            #endregion Public Properties

            #region C-Tor

            public TokenIterator( IList<Token> tokens )
            {
                Tokens = tokens;
            }

            #endregion C-Tor

            #region Public Methods

            public static TokenIterator operator ++( TokenIterator iter )
            {
                return new TokenIterator( iter.Tokens ) { Index = iter.Index + 1 };
            }

            #region Public Methods

            #endregion Private Methods

            private Token GetToken( int index )
            {
                return ( index < Tokens.Count ) ? Tokens[ index ] : null;
            }

            #endregion Private Methods
        }

        #endregion Private Types

        #region Public Types

        /// <summary>
        /// Represents the location of a parameter being passed into a function call
        /// </summary>
        public class Parameter
        {
            #region Public Properties

            public Stub.PositionContext Start { get; private set; } = new Stub.PositionContext();
            public Stub.PositionContext End { get; private set; } = new Stub.PositionContext();

            #endregion Public Properties
        }

        public class Function
        {
            #region Public Properties

            public Stub.Function Resolved { get; set; }
            public Stub.PositionContext Start { get; private set; } = new Stub.PositionContext();
            public Stub.PositionContext End { get; private set; } = new Stub.PositionContext();
            public List<Parameter> Parameters { get; private set; } = new List<Parameter>();

            #endregion Public Properties
        }

        #endregion Public Types

        #region Private Data

        private Stub.Database _database;

        private Lexer _lexer = new Lexer();
        private Listener _listener = new Listener();
        private Local _parser;

        #endregion Private Data

        #region Public Properties

        public bool IsFunctionDeclaration { get; private set; }

        #endregion Public Properties

        #region C-Tor

        public Parser( Stub.Database database, string relativeFilePath )
        {
            _database = database;
            _parser = new Local( database, relativeFilePath );
        }

        #endregion C-Tor

        #region Public Methods

        public List<Function> ResolveFunctions( SnapshotPoint point )
        {
            ITextSnapshotLine line = point.GetContainingLine();
            string text = line.GetText();

            return ResolveFunctions( text, line.Start.Position, line.LineNumber );
        }

        public List<Function> ResolveFunctions( ITrackingSpan trackingSpan, ITextSnapshot snapshot )
        {
            string text = trackingSpan.GetText( snapshot );
            ITextSnapshotLine line = trackingSpan.GetStartPoint( snapshot ).GetContainingLine();

            return ResolveFunctions( text, line.Start.Position, line.LineNumber );
        }

        public List<Function> ResolveFunctions( string text, int lineStartPosition, int lineNumber )
        {
            _listener.Reset( lineStartPosition, lineNumber );

            _lexer.Tokenize( text, _listener );
            _parser.Parse( _listener.Tokens );

            TokenIterator iterator = new TokenIterator( _listener.Tokens );

            List<Function> functions = new List<Function>();

            while ( !iterator.End )
            {
                if ( iterator.Next?.Id == '(' )
                {
                    ResolveCall( ref iterator, functions );
                }
                else if( iterator.Current.Id == /* TOKEN_FUNCTION */ 274 )
                {
                    IsFunctionDeclaration = true;
                }

                ++iterator;
            }

            return functions;
        }

        #endregion Public Methods

        #region Private Methods

        private void ResolveCall( ref TokenIterator iterator, IList<Function> functions )
        {
            Debug.Assert( iterator.Current != null );

            // New function to resolve
            Function function = new Function();

            // Resolve the function identifier
            Stub.Token parsedIdentToken;
            if ( _parser.Results.Tokens.TryGetValue( (int)iterator.Current.End, out parsedIdentToken ) )
            {
                function.Resolved = parsedIdentToken.Declaration as Stub.Function;
            }

            ++iterator;

            Debug.Assert( iterator.Current.Id == '(' );

            function.Start.Start = (int)iterator.Current.Position;
            function.Start.End = (int)iterator.Current.End;

            ResolveCallInternal( function, ref iterator, functions );

            if ( iterator.Current != null )
            {
                function.End.Start = (int)iterator.Current.Position;
                function.End.End = (int)iterator.Current.End;

                if ( iterator.Current.Id == ')' )
                {
                    ++iterator;
                }
            }
            else
            {
                // If we've reached the end of the tokens, account for whitespace by adding 2 to the parameter end position
                function.End.Start = (int)iterator.Previous.Position;
                function.End.End = (int)iterator.Previous.End + 2;
            }

            functions.Add( function );
        }

        private void ResolveCallInternal( Function function, ref TokenIterator iterator, IList<Function> functions )
        {
            Debug.Assert( iterator.Current.Id == '(' );

            while ( !iterator.End && iterator.Current.Id != ')' )
            {
                Parameter parameter = new Parameter();
                parameter.Start.Start = (int)iterator.Current.Position;
                parameter.Start.End = (int)iterator.Current.End;
                ++iterator;

                ResolveParameter( parameter, ref iterator, functions );

                function.Parameters.Add( parameter );
            }
        }

        private void ResolveParameter( Parameter parameter, ref TokenIterator iterator, IList<Function> functions )
        {
            while ( !iterator.End && iterator.Current.Id != ',' && iterator.Current.Id != ')' )
            {
                if ( iterator.Next?.Id == '(' )
                {
                    ResolveCall( ref iterator, functions );
                }
                else
                {
                    ++iterator;
                }
            }

            if ( iterator.Current != null )
            {
                parameter.End.Start = (int)iterator.Current.Position;
                parameter.End.End = (int)iterator.Current.End;
            }
            else
            {
                // If we've reached the end of the tokens, account for whitespace by adding 2 to the parameter end position
                parameter.End.Start = (int)iterator.Previous.End;
                parameter.End.End = (int)iterator.Previous.End + 2;
            }
        }

        #endregion Private Methods
    }
}
